﻿using NDesk.Options;
using Rhetos.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;

namespace Helpers.Common
{
    /// <summary>
    /// Contains a set of common helpers methods.
    /// </summary>
    public static class HelperCommon
    {
        //#region Static Properties

        //public readonly static List<Tuple<string, string>> DateTimeTokens = new List<Tuple<string, string>>
        //{
        //    new Tuple<string, string>("{month}", ""),
        //};

        //#endregion

        #region Static Fields

        private static bool[] commonCharacters;

        #endregion

        static HelperCommon()
        {
            #region Fill common characters array

            HelperCommon.commonCharacters = new bool[65535];

            for (char c = '0'; c <= '9'; c++)
                HelperCommon.commonCharacters[c] = true;

            for (char c = 'A'; c <= 'Z'; c++)
                HelperCommon.commonCharacters[c] = true;

            for (char c = 'a'; c <= 'z'; c++)
                HelperCommon.commonCharacters[c] = true;

            #endregion
        }

        #region Static Methods

        public static int BooleanToInt(bool value)
        {
            return (value == true)
                        ? 1
                        : 0;
        }
        public static string BuildServiceUrl(string protocol, string host, int port, string urlEnd)
        {
            string urlTemplate = "{0}://{1}:{2}/{3}/";
            string url = "";

            /* If the host of the service is null or empty configure it for any ip hmiServiceUrl. */
            url = string.Format(
                urlTemplate
                , protocol
                , (string.IsNullOrEmpty(host) == true)
                        ? "localhost"//IPAddress.Any.ToString()
                        : host
                , port
                , urlEnd
            );

            return url;
        }
        public static T CloneObject<T>(object source, Type targetType)
        {
            T target = (T)targetType.Assembly.CreateInstance(targetType.FullName);

            if (source != null)
            {
                FieldInfo[] fieldsInfo = targetType.GetFields(BindingFlags.Instance | BindingFlags.Public);

                foreach (FieldInfo fieldInfo in fieldsInfo)
                {
                    fieldInfo.SetValue(
                        target
                        , source.GetType().GetProperty(fieldInfo.Name).GetValue(source, null)
                    );
                }
            }

            return target;
        }
        /// <summary>
        /// Creates and returns a deep clone of the object passed by parameters. The class has to be 
        /// serializable for this method to work.
        /// </summary>
        /// <typeparam name="T">Object Type</typeparam>
        /// <param name="obj">Object instance</param>
        /// <returns>Deep clone of object</returns>
        public static T DeepClone<T>(T obj)
        {
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T)formatter.Deserialize(ms);
            }
        }
        public static ObservableCollection<ItemsType> EnumerableToObservableCollection<ItemsType>(IEnumerable<ItemsType> source)
        {
            ObservableCollection<ItemsType> observColl = null;

            if (source != null)
            {
                observColl = new ObservableCollection<ItemsType>();

                source
                    .ToList()
                    .ForEach(observColl.Add);
            }

            return observColl;
        }
        public static void ExtendObject<T>(object source, object target)
        {
            Type type = typeof(T);

            if (source != null)
            {
                FieldInfo[] fieldsInfo = type.GetFields(BindingFlags.Instance | BindingFlags.Public);

                foreach (FieldInfo fieldInfo in fieldsInfo)
                {
                    fieldInfo.SetValue(
                        target
                        , source.GetType().GetProperty(fieldInfo.Name).GetValue(source, null)
                    );
                }
            }
        }
        public static string FillSliceStringWith(string str, int length, Pad pad, string fill)
        {
            if (str == null) str = "";
            if (str.Length > length) str = str.Substring(0, length);
            else
            {
                Int32 t = length - str.Length;
                string strSpaces = "";
                for (var i = 0; i < t; i++) strSpaces += fill;
                switch (pad)
                {
                    case Pad.Right:
                        str = str + strSpaces;
                        break;
                    case Pad.Left:
                        str = strSpaces + str;
                        break;
                }

            }

            return str;
        }
        public static string GetEnumName<EnumType>(EnumType enumValue)
        {
            return Enum.GetName(typeof(EnumType), enumValue);
        }
        public static EnumType GetEnumByName<EnumType>(string enumName)
        {
            return (EnumType)System.Enum.Parse(typeof(EnumType), enumName);
        }
        public static List<T> GetEnumValues<T>()
        {
            return Enum.GetValues(typeof(T))
                .Cast<T>()
                .ToList();
        }
        /// <summary>
        /// Gets the full path of the executing executable.
        /// </summary>
        /// <returns>The full path of the executing executable.</returns>
        public static string GetExecutingExePath()
        {
            return AppDomain.CurrentDomain.BaseDirectory;
        }
        /// <summary>
        /// Returns the absolute path for the specified path string in the context of the executing application.
        /// </summary>
        /// <param name="path">The file or directory for which to obtain absolute path information.</param>
        /// <returns>A string containing the fully qualified location of path, such as "C:\MyFile.txt".</returns>
        public static string GetFullPath(string path)
        {
            return Path.Combine(HelperCommon.GetExecutingExePath(), path);
        }
        public static string ParseIntToChar(int? value)
        {
            string result = "";

            if (value != null)
                result = (Convert.ToChar(value + 64)).ToString();

            return result;
        }
        public static string NullableDatetimeToTrimedString(Nullable<DateTime> value)
        {
            return HelperCommon.NullableDatetimeToTrimedString(value, "null");
        }
        public static string NullableDatetimeToTrimedString(Nullable<DateTime> value, string defaultValue)
        {
            return (value.HasValue == true)
                        ? value.Value.ToString("dd/MMM/yyyyHH:mm:ss").ToUpper()
                        : defaultValue.Trim();
        }
        public static string NullableIntToTrimedString(int? value)
        {
            return HelperCommon.NullableIntToTrimedString(value, "null");
        }
        public static string NullableIntToTrimedString(int? value, string defaultValue)
        {
            return (value.HasValue == true)
                        ? value.Value.ToString().Trim()
                        : defaultValue.Trim();
        }
        public static string NullableObjectToTrimedString(object value)
        {
            return HelperCommon.NullableObjectToTrimedString(value, "null");
        }
        public static string NullableObjectToTrimedString(object value, string defaultValue)
        {
            return (value != null)
                        ? HelperCommon.ObjectToString(value)
                        : defaultValue.Trim();
        }
        public static ObjType NullToNotNull<ObjType>(ObjType value, ObjType defaultValue)
        {
            return (value != null)
                        ? value
                        : defaultValue;
        }
        /// <summary>
        /// Returns the string representation of the source object with all its properties.
        /// </summary>
        /// <param name="source">The object to stringify.</param>
        /// <returns>The string representation of the object specified.</returns>
        public static string ObjectToString(object source)
        {
            return HelperCommon.ObjectToString(source, "{0}: {1}");
        }
        /// <summary>
        /// Returns the string representation of the source object with all its properties.
        /// </summary>
        /// <param name="source">The object to stringify.</param>
        /// <param name="propertyFormat">Format string to be applied to the source object properties.</param>
        /// <returns>The string representation of the object specified.</returns>
        public static string ObjectToString(object source, string propertyFormat)
        {
            return HelperCommon.ObjectToString(source, propertyFormat, ", ");
        }
        /// <summary>
        /// Returns the string representation of the source object with all its properties.
        /// </summary>
        /// <param name="source">The object to stringify.</param>
        /// <param name="propertyFormat">Format string to be applied to the source object properties.</param>
        /// <param name="separator">String to be used for properties separator.</param>
        /// <returns>The string representation of the object specified.</returns>
        public static string ObjectToString(object source, string propertyFormat, string separator)
        {
            string objString = "";

            propertyFormat += separator;

            if (source != null)
            {
                if ((source is IEnumerable) == false)
                {
                    var sourcePropsDesc = TypeDescriptor.GetProperties(source);

                    if (sourcePropsDesc.Count > 0)
                    {
                        foreach (PropertyDescriptor sourcePropDesc in sourcePropsDesc)
                        {
                            object propValue = sourcePropDesc.GetValue(source);
                            string stringValue;


                            if (propValue != null)
                            {
                                if ((propValue is string) == true)
                                    stringValue = "\"" + propValue.ToString() + "\"";
                                else if ((propValue is IEnumerable) == false)
                                    stringValue = propValue.ToString();
                                else
                                    stringValue = "[" + ((IEnumerable)propValue).ToLog() + "]";
                            }
                            else
                            {
                                stringValue = "null";
                            }


                            objString += string.Format(propertyFormat, sourcePropDesc.Name, stringValue);
                        }

                        if (string.IsNullOrWhiteSpace(objString) == false)
                            objString = objString.Substring(0, objString.Length - 2);
                    }
                    else
                    {
                        if ((source is string) == true)
                            objString += "\"" + source.ToString() + "\"";
                        else
                            objString += source.ToString();
                    }
                }
                else
                {
                    if (source is string)
                        objString += "\"" + source.ToString() + "\"";
                    else
                        objString += "[" + ((IEnumerable)source).ToLog() + "]";
                }
            }

            return objString;
        }
        public static void PrintProperties(object element, int indent = 0)
        {
            var properties = element.GetType().GetProperties().OrderBy(p => p.Name);
            foreach (var property in properties)
            {
                if (property.PropertyType.IsPrimitive ||
                    property.PropertyType == typeof(DateTime) ||
                    property.PropertyType == typeof(string) ||
                    property.PropertyType == typeof(BitArray))
                {
                    Debug.Print("{2}{0} = {1}", property.Name, property.GetValue(element, null), new string('\t', indent));
                }
                else if (property.PropertyType.IsArray)
                {
                    Debug.Print("{1}{0} = ", property.Name, new string('\t', indent));
                    var array = (Array)property.GetValue(element, null);
                    for (var itemIndx = 0; itemIndx < array.Length; itemIndx++)
                    {
                        Debug.Print("{2}{0}[{3}] = {1}", property.Name, array.GetValue(itemIndx), new string('\t', indent + 1), itemIndx);
                    }
                }
                else if (property.PropertyType == typeof(BitArray))
                {
                    Debug.Print("{1}{0} = ", property.Name, new string('\t', indent));
                    var array = (BitArray)property.GetValue(element, null);
                    for (var itemIndx = 0; itemIndx < array.Length; itemIndx++)
                    {
                        Debug.Print("{2}{0}[{3}] = {1}", property.Name, array[itemIndx], new string('\t', indent + 1), itemIndx);
                    }
                }
                else
                {
                    Debug.Print("{1}{0} = ", property.Name, new string('\t', indent));
                    PrintProperties(property.GetValue(element, null), indent + 1);
                }
            }
        }
        public static string RemoveDiacritics(string text)
        {
            var normalizedString = text.Normalize(NormalizationForm.FormD);
            var stringBuilder = new StringBuilder();

            foreach (var c in normalizedString)
            {
                var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);

                if (unicodeCategory != UnicodeCategory.NonSpacingMark)
                    stringBuilder.Append(c);
            }

            return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
        }
        public static string RemoveSpecialCharacters(string str)
        {
            char[] buffer = new char[str.Length];
            int index = 0;

            foreach (char c in str)
            {
                if (HelperCommon.commonCharacters[c])
                {
                    buffer[index] = c;
                    index++;
                }
            }

            return new string(buffer, 0, index);
        }
        public static string ReplaceDateTimeTokens(string text, DateTime datetime)
        {
            return HelperCommon.ReplaceDateTimeTokens(text, datetime, CultureInfo.CurrentCulture, "");
        }
        public static string ReplaceDateTimeTokens(string text, DateTime datetime, CultureInfo cultureInfo)
        {
            return HelperCommon.ReplaceDateTimeTokens(text, datetime, cultureInfo, "");
        }
        public static string ReplaceDateTimeTokens(string text, DateTime datetime, CultureInfo cultureInfo, string prefix)
        {
            var fastReplacer = new FastReplacer("{", "}");

            fastReplacer.Append(text);

            fastReplacer.Replace("{" + prefix + "year-y}", datetime.ToString("y", cultureInfo));
            fastReplacer.Replace("{" + prefix + "year-yy}", datetime.ToString("yy", cultureInfo));
            fastReplacer.Replace("{" + prefix + "year-yyy}", datetime.ToString("yyy", cultureInfo));
            fastReplacer.Replace("{" + prefix + "year-yyyy}", datetime.ToString("yyyy", cultureInfo));
            fastReplacer.Replace("{" + prefix + "month-M}", datetime.ToString("M", cultureInfo));
            fastReplacer.Replace("{" + prefix + "month-MM}", datetime.ToString("MM", cultureInfo));
            fastReplacer.Replace("{" + prefix + "month-MMM}", datetime.ToString("MMM", cultureInfo));
            fastReplacer.Replace("{" + prefix + "month-MMMM}", datetime.ToString("MMMM", cultureInfo));
            fastReplacer.Replace("{" + prefix + "day-d}", datetime.ToString("d", cultureInfo));
            fastReplacer.Replace("{" + prefix + "day-dd}", datetime.ToString("dd", cultureInfo));
            fastReplacer.Replace("{" + prefix + "day-ddd}", datetime.ToString("ddd", cultureInfo));
            fastReplacer.Replace("{" + prefix + "day-dddd}", datetime.ToString("dddd", cultureInfo));
            //fastReplacer.Replace("{" + prefix + "hour-h}", datetime.ToString("h", cultureInfo));
            fastReplacer.Replace("{" + prefix + "hour-hh}", datetime.ToString("hh", cultureInfo));
            //fastReplacer.Replace("{" + prefix + "hour-H}", datetime.ToString("H", cultureInfo));
            fastReplacer.Replace("{" + prefix + "hour-HH}", datetime.ToString("HH", cultureInfo));
            fastReplacer.Replace("{" + prefix + "minute-m}", datetime.ToString("m", cultureInfo));
            fastReplacer.Replace("{" + prefix + "minute-mm}", datetime.ToString("mm", cultureInfo));
            fastReplacer.Replace("{" + prefix + "second-s}", datetime.ToString("s", cultureInfo));
            fastReplacer.Replace("{" + prefix + "second-ss}", datetime.ToString("ss", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-f}", datetime.ToString("f", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-ff}", datetime.ToString("ff", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-fff}", datetime.ToString("fff", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-ffff}", datetime.ToString("ffff", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-F}", datetime.ToString("F", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-FF}", datetime.ToString("FF", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-FFF}", datetime.ToString("FFF", cultureInfo));
            fastReplacer.Replace("{" + prefix + "sec-frac-FFFF}", datetime.ToString("FFFF", cultureInfo));
            fastReplacer.Replace("{" + prefix + "ampm-t}", datetime.ToString("t", cultureInfo));
            fastReplacer.Replace("{" + prefix + "ampm-tt}", datetime.ToString("tt", cultureInfo));
            //fastReplacer.Replace("{" + prefix + "zone-z}", datetime.ToString("z", cultureInfo));
            fastReplacer.Replace("{" + prefix + "zone-zz}", datetime.ToString("zz", cultureInfo));
            fastReplacer.Replace("{" + prefix + "zone-zzz}", datetime.ToString("zzz", cultureInfo));

            return fastReplacer.ToString();
        }

        /// <summary>
        /// Retrieves the NetBIOS name of the local computer. This name is established at system startup,
        /// when the system reads it from the registry.
        /// </summary>
        /// <param name="buffer">A pointer to a buffer that receives the computer name or the cluster virtual server name.
        /// The buffer size should be large enough to contain MAX_COMPUTERNAME_LENGTH + 1 characters.</param>
        /// <param name="size">On input, specifies the size of the buffer, in TCHARs. On output, the number of TCHARs copied to the
        /// destination buffer, not including the terminating null character. If the buffer is too small, the function fails and
        /// GetLastError returns ERROR_BUFFER_OVERFLOW. The lpnSize parameter specifies the size of the buffer required,
        /// including the terminating null character.</param>
        /// <returns>If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero.
        /// To get extended error information, call GetLastError.</returns>
        [DllImport("Kernel32", CharSet = CharSet.Auto)]
        public static extern bool GetComputerName(StringBuilder buffer, ref uint size);

        #endregion
    }
}
